﻿using System;
using System.Globalization;
using System.Linq;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using NuclearWinter.GameFlow;
using StoCombatLogHUD.Parsing;
using StoCombatLogHUD.Toolkit;
using StoCombatLogHUD.Toolkit.ExtensionMethods;
using NuclearUI = NuclearWinter.UI;

namespace StoCombatLogHUD.GameStates
{
	/// <summary>
	/// </summary>
	public class SummaryDisplayState : GameStateFadeTransition<Main>
	{
		/// <summary>
		///   Initializes a new instance of the <see cref="DetailDisplayState" /> class.
		/// </summary>
		/// <param name="game">The game.</param>
		public SummaryDisplayState(Main game)
			: base(game)
		{
			SummaryStatistics = new NuclearUI.ListView(Game.MainScreen)
				{
					SelectHandler = OnPlayerSelected,
					AnchoredRect = NuclearUI.AnchoredRect.CreateTopLeftAnchored(0, 0, Game.MainScreen.Width - 50, 300)
				};
			SummaryStatistics.Columns.AddRange(new[]
				{
					new NuclearUI.ListViewColumn(SummaryStatistics, "Player", 300, NuclearUI.Anchor.Center)
				                                   .Perform(column => column.Label.Font = Game.UIStyle.LargeFont),
					new NuclearUI.ListViewColumn(SummaryStatistics, "Total Damage", 150, NuclearUI.Anchor.Center)
				                                   .Perform(column => column.Label.Font = Game.UIStyle.LargeFont),
					new NuclearUI.ListViewColumn(SummaryStatistics, "DPS", 110, NuclearUI.Anchor.Center)
				                                   .Perform(column => column.Label.Font = Game.UIStyle.LargeFont)
				});

			StartTimeStampLabel = new NuclearUI.Label(Game.MainScreen)
				{
					Font = Game.UIStyle.SmallFont,
					Color = Color.WhiteSmoke
				};

			EndTimeStampLabel = new NuclearUI.Label(Game.MainScreen)
				{
					Font = Game.UIStyle.SmallFont,
					Color = Color.WhiteSmoke
				};

			TrackedTimeLabel = new NuclearUI.Label(Game.MainScreen)
				{
					Font = Game.UIStyle.SmallFont,
					Color = Color.WhiteSmoke
				};

			SetLabel(StartTimeStampLabel, new DateTime());
			SetLabel(EndTimeStampLabel, new DateTime());
			SetLabel(TrackedTimeLabel, new TimeSpan());

			var openLiveLogFileButton = new NuclearUI.Button(Game.MainScreen, "Open Live CombatLog")
				{
					Font = Game.UIStyle.MediumFont,
					TooltipText = "Opens the live combatlog file set in the configuration file",
					ClickHandler = button => OnOpenLiveLogFileClicked()
				};

			var loadLogFileButton = new NuclearUI.Button(Game.MainScreen, "Import from Combatlog")
				{
					Font = Game.UIStyle.MediumFont,
					TooltipText = "Imports statistics from a specified combatlog",
					ClickHandler = button => OnImportFromCombatlogClicked()
				};

			var saveLogFileButton = new NuclearUI.Button(Game.MainScreen, "Export to Combatlog")
				{
					Font = Game.UIStyle.MediumFont,
					TooltipText = "Exports statistics to a specified combatlog",
					ClickHandler = button => OnExportToCombatLogClicked()
				};

			var copyToClipboardButton = new NuclearUI.Button(Game.MainScreen, "Copy to Clipboard")
				{
					Font = Game.UIStyle.MediumFont,
					TooltipText = "Copies the summary to the clipboard",
					ClickHandler = button => OnCopyToClipboardClicked()
				};

			var clearDisplayButton = new NuclearUI.Button(Game.MainScreen, "Clear Display")
				{
					Font = Game.UIStyle.MediumFont,
					TooltipText = "Clears the displayed data and resets the DPS counter",
					ClickHandler = button => OnClearDisplayClicked()
				};

			var clearLogFileButton = new NuclearUI.Button(Game.MainScreen, "Clear LogFile")
				{
					Font = Game.UIStyle.MediumFont,
					TooltipText = "Clears the whole combatlog.log file",
					ClickHandler = button => OnClearLogfileClicked()
				};

			var exitButton = new NuclearUI.Button(Game.MainScreen, "Exit")
				{
					Font = Game.UIStyle.LargeFont,
					ClickHandler = button => OnExitClicked()
				};

			ErrorLabel = new NuclearUI.Label(Game.MainScreen, string.Empty, NuclearUI.Anchor.Center, Color.DarkRed)
				{
					Font = Game.UIStyle.ExtraLargeFont,
					AnchoredRect = NuclearUI.AnchoredRect.CreateTopLeftAnchored(50, 100, 500, 100)
				};

			DisplayPanel = new NuclearUI.Panel(Game.MainScreen, Game.UIStyle.Panel, Game.UIStyle.PanelCornerSize)
				.Perform(
					mainPanel =>
						{
							mainPanel.AddChild(ErrorLabel);
							mainPanel.AddChild(
								new NuclearUI.BoxGroup(Game.MainScreen, NuclearUI.Orientation.Vertical, 10, NuclearUI.Anchor.Start)
									{
										AnchoredRect = NuclearUI.AnchoredRect.CreateFull(5)
									}
									.Perform(
										mainGroup =>
											{
												mainGroup.AddChild(SummaryStatistics);
												mainGroup.AddChild(
													new NuclearUI.BoxGroup(Game.MainScreen, NuclearUI.Orientation.Horizontal, 0, NuclearUI.Anchor.Start)
														.Perform(
															group =>
																{
																	group.AddChild(
																		openLiveLogFileButton.
																			Perform(
																				button =>
																				button.AnchoredRect =
																				NuclearUI.AnchoredRect.CreateTopLeftAnchored(10, 0, 220, Game.UIStyle.DefaultButtonHeight)));
																	group.AddChild(
																		copyToClipboardButton.
																			Perform(
																				button =>
																				button.AnchoredRect =
																				NuclearUI.AnchoredRect.CreateTopLeftAnchored(10, 0, 220, Game.UIStyle.DefaultButtonHeight)));
																}));
												mainGroup.AddChild(
													new NuclearUI.BoxGroup(Game.MainScreen, NuclearUI.Orientation.Horizontal, 0, NuclearUI.Anchor.Start)
														.Perform(
															group =>
																{
																	group.AddChild(
																		loadLogFileButton.
																			Perform(
																				button =>
																				button.AnchoredRect =
																				NuclearUI.AnchoredRect.CreateTopLeftAnchored(10, 0, 220, Game.UIStyle.DefaultButtonHeight)));
																	group.AddChild(
																		clearDisplayButton
																			.Perform(
																				button =>
																				button.AnchoredRect =
																				NuclearUI.AnchoredRect.CreateTopLeftAnchored(10, 0, 220, Game.UIStyle.DefaultButtonHeight)));
																}));
												mainGroup.AddChild(
													new NuclearUI.BoxGroup(Game.MainScreen, NuclearUI.Orientation.Horizontal, 0, NuclearUI.Anchor.Start)
														.Perform(
															group =>
																{
																	group.AddChild(
																		saveLogFileButton.
																			Perform(
																				button =>
																				button.AnchoredRect =
																				NuclearUI.AnchoredRect.CreateTopLeftAnchored(10, 0, 220, Game.UIStyle.DefaultButtonHeight)));
																	group.AddChild(
																		clearLogFileButton
																			.Perform(
																				button =>
																				button.AnchoredRect =
																				NuclearUI.AnchoredRect.CreateTopLeftAnchored(10, 0, 220, Game.UIStyle.DefaultButtonHeight)));
																	group.AddChild(
																		exitButton
																			.Perform(
																				button =>
																				button.AnchoredRect =
																				NuclearUI.AnchoredRect.CreateBottomLeftAnchored(20, -6, 110, 80)));
																}));
												mainGroup.AddChild(
													new NuclearUI.BoxGroup(Game.MainScreen, NuclearUI.Orientation.Horizontal, 0)
														.Perform(
															group =>
																{
																	group.AddChild(
																		new NuclearUI.Panel(Game.MainScreen, game.UIStyle.PopupFrame, 10)
																			{
																				AnchoredRect = NuclearUI.AnchoredRect.CreateBottomLeftAnchored(0, -45, 220, 40)
																			}
																			.Perform(panel => panel.AddChild(StartTimeStampLabel)));
																	group.AddChild(
																		new NuclearUI.Panel(Game.MainScreen, game.UIStyle.PopupFrame, 10)
																			{
																				AnchoredRect = NuclearUI.AnchoredRect.CreateBottomLeftAnchored(0, -45, 220, 40)
																			}
																			.Perform(panel => panel.AddChild(EndTimeStampLabel)));
																	group.AddChild(
																		new NuclearUI.Panel(Game.MainScreen, game.UIStyle.PopupFrame, 10)
																			{
																				AnchoredRect = NuclearUI.AnchoredRect.CreateBottomLeftAnchored(0, -45, 180, 40)
																			}
																			.Perform(panel => panel.AddChild(TrackedTimeLabel)));
																}));
											}));
						});

			DisplayPanel.DoLayout(DisplayPanel.LayoutRect);
		}

		/// <summary>
		///   Gets or sets the detailed statistics.
		/// </summary>
		/// <value>
		///   The detailed statistics.
		/// </value>
		private NuclearUI.ListView SummaryStatistics { get; set; }

		/// <summary>
		///   Gets or sets the display panel.
		/// </summary>
		/// <value>
		///   The display panel.
		/// </value>
		private NuclearUI.Panel DisplayPanel { get; set; }

		/// <summary>
		///   Gets or sets the start time stamp label.
		/// </summary>
		/// <value>
		///   The start time stamp label.
		/// </value>
		private NuclearUI.Label StartTimeStampLabel { get; set; }

		/// <summary>
		///   Gets or sets the end time stamp label.
		/// </summary>
		/// <value>
		///   The end time stamp label.
		/// </value>
		private NuclearUI.Label EndTimeStampLabel { get; set; }

		/// <summary>
		///   Gets or sets the tracked time label.
		/// </summary>
		/// <value>
		///   The tracked time label.
		/// </value>
		private NuclearUI.Label TrackedTimeLabel { get; set; }

		/// <summary>
		///   Gets or sets the error label.
		/// </summary>
		/// <value>
		///   The error label.
		/// </value>
		private NuclearUI.Label ErrorLabel { get; set; }

		/// <summary>
		///   Updates this instance.
		/// </summary>
		/// <param name="elapsedTime">The elapsed time.</param>
		public override void Update(float elapsedTime)
		{
			if (Game.CombatLogStatistics == null)
			{
				SummaryStatistics.Rows.Clear();
				ErrorLabel.Text = "Combatlog file could not be opened.";
			}
			else
			{
				ErrorLabel.Text = string.Empty;

				if (HasCombatLogChanged())
					UpdateStatisticsDisplay();
			}

			DisplayPanel.Update(elapsedTime);
			DisplayPanel.DoLayout(DisplayPanel.LayoutRect);
		}

		/// <summary>
		///   Updates the statistics display.
		/// </summary>
		private void UpdateStatisticsDisplay()
		{
			SetLabel(StartTimeStampLabel, Game.CombatLogStatistics.StartTimeStamp);
			SetLabel(EndTimeStampLabel, Game.CombatLogStatistics.EndTimeStamp);
			SetLabel(TrackedTimeLabel, Game.CombatLogStatistics.EndTimeStamp - Game.CombatLogStatistics.StartTimeStamp);

			var orderedStatistics =
				Game.CombatLogStatistics.Statistics.OrderByDescending(pair => pair.Value.TotalDamage[CombatLogEntry.CombatFlags.Any].ValuePerSecond);

			SummaryStatistics.Rows.Clear();
			foreach (var playerStatistics in orderedStatistics)
			{
				SummaryStatistics.Rows.Add(new NuclearUI.ListViewRow(
					                           new NuclearUI.ListViewCell[]
						                           {
							                           new NuclearUI.ListViewTextCell(SummaryStatistics, playerStatistics.Key),
							                           new NuclearUI.ListViewTextCell(SummaryStatistics,
							                                                          GameHelpers.StatisticsValueToString(
								                                                          playerStatistics.Value.TotalDamage[CombatLogEntry.CombatFlags.Any]
									                                                          .TotalValue)),
							                           new NuclearUI.ListViewTextCell(SummaryStatistics,
							                                                          GameHelpers.StatisticsValueToString(
								                                                          playerStatistics.Value.TotalDamage[CombatLogEntry.CombatFlags.Any]
									                                                          .ValuePerSecond))
						                           },
					                           playerStatistics.Key));
			}
		}

		/// <summary>
		///   Draws this instance.
		/// </summary>
		public override void Draw()
		{
			// Nothing to do here
		}

		/// <summary>
		///   Starts this instance.
		/// </summary>
		public override void Start()
		{
			Game.SetMainScreenContent(DisplayPanel);
			Game.CombatLogChanged += OnCombatLogChanged;
			combatLogChanged = true;

			base.Start();
		}

		/// <summary>
		///   Stops this instance.
		/// </summary>
		public override void Stop()
		{
			Game.CombatLogChanged -= OnCombatLogChanged;

			base.Stop();
		}

		/// <summary>
		///   Called when a player is selected in the ListView.
		/// </summary>
		/// <param name="listView">The list view.</param>
		private void OnPlayerSelected(NuclearUI.ListView listView)
		{
			Game.GameStateManager.DisplayDetails(listView.SelectedRow.Tag as string);
		}

		/// <summary>
		/// Called when the "open live logfile" button is clicked.
		/// </summary>
		private void OnOpenLiveLogFileClicked()
		{
			Game.CloseCombatlog();
			Game.InitializeCombatlog(Game.Configuration.CombatLogPath);

			OnCombatLogChanged(this, EventArgs.Empty);
		}

		/// <summary>
		/// Called when the "import from combatlog" button is clicked.
		/// </summary>
		private void OnImportFromCombatlogClicked()
		{
			var logFilePath = GetFileNameFromUserInput(isSaveOperation: false);
			if (logFilePath == null)
				return;

			Game.CloseCombatlog();
			Game.InitializeCombatlog(logFilePath);

			OnCombatLogChanged(this, EventArgs.Empty);
		}

		/// <summary>
		/// Called when the "export to combatlog" button is clicked.
		/// </summary>
		private void OnExportToCombatLogClicked()
		{
			if (Game.CombatLogStatistics == null)
				return;

			var logFilePath = GetFileNameFromUserInput(isSaveOperation: true);
			if (logFilePath == null)
				return;

			Game.CombatLogStatistics.ExportToCombatLog(logFilePath);
		}

		/// <summary>
		/// Called when the "copy to clipboard" button is clicked.
		/// </summary>
		private void OnCopyToClipboardClicked()
		{
			if (Game.CombatLogStatistics == null)
				return;

			var orderedStatistics =
				Game.CombatLogStatistics.Statistics.OrderByDescending(pair => pair.Value.TotalDamage[CombatLogEntry.CombatFlags.Any].ValuePerSecond);

			var text = string.Empty;
			foreach (var statistic in orderedStatistics)
			{
				if (text.Length > 0)
					text += ", ";
				text += string.Format("{0}: {1}dps {2}hps",
				                      statistic.Key.Split('@').Last(),
				                      GameHelpers.StatisticsValueToString(statistic.Value.TotalDamage[CombatLogEntry.CombatFlags.Any].ValuePerSecond),
				                      GameHelpers.StatisticsValueToString(statistic.Value.TotalHealing[CombatLogEntry.CombatFlags.Any].ValuePerSecond));
			}
			Clipboard.SetText(text);
		}

		/// <summary>
		/// Called when the "clear combatlog" button is clicked.
		/// </summary>
		private void OnClearDisplayClicked()
		{
			if (Game.CombatLogStatistics == null)
				return;

			Game.RunInExceptionHandlingContext(() => Game.CombatLogStatistics.ResetStatistics());

			OnCombatLogChanged(this, EventArgs.Empty);
		}

		/// <summary>
		/// Called when "clear logfile" button is clicked.
		/// </summary>
		private void OnClearLogfileClicked()
		{
			if (Game.CombatLogStatistics == null)
				return;

			Game.RunInExceptionHandlingContext(
				() =>
					{
						var logFilePath = Game.CombatLogStatistics.CombatLogParser.LogFilePath;
						Game.CloseCombatlog();
						GameHelpers.ClearFileContents(logFilePath);
						Game.InitializeCombatlog(logFilePath);
					});

			OnCombatLogChanged(this, EventArgs.Empty);
		}

		/// <summary>
		///   Called when the exit button is clicked.
		/// </summary>
		private void OnExitClicked()
		{
			Game.Exit();
		}

		/// <summary>
		///   Sets a timestamp label.
		/// </summary>
		/// <param name="label">The label.</param>
		/// <param name="dateTime">The DateTime.</param>
		/// <returns></returns>
		private void SetLabel(NuclearUI.Label label, DateTime dateTime)
		{
			string labelTitle = string.Empty;

			if (label == StartTimeStampLabel)
				labelTitle = "Begin";

			if (label == EndTimeStampLabel)
				labelTitle = "End";

			label.Text = string.Format("{0}: {1}",
			                           labelTitle,
			                           dateTime.Ticks == 0
				                           ? "-"
				                           : dateTime.ToString(CultureInfo.InvariantCulture));
		}

		/// <summary>
		///   Sets a timespan label.
		/// </summary>
		/// <param name="label">The label.</param>
		/// <param name="timeSpan">The DateTime.</param>
		/// <returns></returns>
		private static void SetLabel(NuclearUI.Label label, TimeSpan timeSpan)
		{
			const string labelTitle = "Tracked";

			label.Text = string.Format("{0}: {1}",
			                           labelTitle,
			                           timeSpan.Ticks == 0
				                           ? "-"
				                           : timeSpan.ToString(@"hh\:mm\:ss"));
		}

		/// <summary>
		/// Gets a file name from user input.
		/// </summary>
		/// <returns></returns>
		private static string GetFileNameFromUserInput(bool isSaveOperation)
		{
			var dialog = isSaveOperation
				             ? new SaveFileDialog()
				             : new OpenFileDialog() as FileDialog;
			dialog.DefaultExt = ".log";
			dialog.FilterIndex = 0;
			dialog.Filter = "Log Files (*.log)|*.log";

			var result = dialog.ShowDialog();

			return result != DialogResult.OK
				       ? null
				       : dialog.FileName;
		}

		#region CombatLog Changed Detection

		private readonly object combatLogLockObject = new object();
		private bool combatLogChanged;

		/// <summary>
		///   Called when [combat log changed].
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="args">
		///   The <see cref="EventArgs" /> instance containing the event data.
		/// </param>
		private void OnCombatLogChanged(object sender, EventArgs args)
		{
			lock (combatLogLockObject)
			{
				combatLogChanged = true;
			}
		}

		/// <summary>
		///   Determines whether the combat log has changed.
		/// </summary>
		/// <returns>
		///   <c>true</c> if the combat log has changed; otherwise, <c>false</c>.
		/// </returns>
		private bool HasCombatLogChanged()
		{
			lock (combatLogLockObject)
			{
				if (combatLogChanged)
				{
					combatLogChanged = false;
					return true;
				}
			}

			return false;
		}

		#endregion
	}
}
